// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/threading/thread_checker.h"

#include <memory>

#include "base/logging.h"
#include "base/macros.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"

// Duplicated from base/threading/thread_checker.h so that we can be
// good citizens there and undef the macro.
#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
#define ENABLE_THREAD_CHECKER 1
#else
#define ENABLE_THREAD_CHECKER 0
#endif

namespace base {

namespace {

    // Simple class to exercise the basics of ThreadChecker.
    // Both the destructor and DoStuff should verify that they were
    // called on the same thread as the constructor.
    class ThreadCheckerClass : public ThreadChecker {
    public:
        ThreadCheckerClass() { }

        // Verifies that it was called on the same thread as the constructor.
        void DoStuff()
        {
            DCHECK(CalledOnValidThread());
        }

        void DetachFromThread()
        {
            ThreadChecker::DetachFromThread();
        }

        static void MethodOnDifferentThreadImpl();
        static void DetachThenCallFromDifferentThreadImpl();

    private:
        DISALLOW_COPY_AND_ASSIGN(ThreadCheckerClass);
    };

    // Calls ThreadCheckerClass::DoStuff on another thread.
    class CallDoStuffOnThread : public base::SimpleThread {
    public:
        explicit CallDoStuffOnThread(ThreadCheckerClass* thread_checker_class)
            : SimpleThread("call_do_stuff_on_thread")
            , thread_checker_class_(thread_checker_class)
        {
        }

        void Run() override { thread_checker_class_->DoStuff(); }

    private:
        ThreadCheckerClass* thread_checker_class_;

        DISALLOW_COPY_AND_ASSIGN(CallDoStuffOnThread);
    };

    // Deletes ThreadCheckerClass on a different thread.
    class DeleteThreadCheckerClassOnThread : public base::SimpleThread {
    public:
        explicit DeleteThreadCheckerClassOnThread(
            ThreadCheckerClass* thread_checker_class)
            : SimpleThread("delete_thread_checker_class_on_thread")
            , thread_checker_class_(thread_checker_class)
        {
        }

        void Run() override { thread_checker_class_.reset(); }

    private:
        std::unique_ptr<ThreadCheckerClass> thread_checker_class_;

        DISALLOW_COPY_AND_ASSIGN(DeleteThreadCheckerClassOnThread);
    };

} // namespace

TEST(ThreadCheckerTest, CallsAllowedOnSameThread)
{
    std::unique_ptr<ThreadCheckerClass> thread_checker_class(
        new ThreadCheckerClass);

    // Verify that DoStuff doesn't assert.
    thread_checker_class->DoStuff();

    // Verify that the destructor doesn't assert.
    thread_checker_class.reset();
}

TEST(ThreadCheckerTest, DestructorAllowedOnDifferentThread)
{
    std::unique_ptr<ThreadCheckerClass> thread_checker_class(
        new ThreadCheckerClass);

    // Verify that the destructor doesn't assert
    // when called on a different thread.
    DeleteThreadCheckerClassOnThread delete_on_thread(
        thread_checker_class.release());

    delete_on_thread.Start();
    delete_on_thread.Join();
}

TEST(ThreadCheckerTest, DetachFromThread)
{
    std::unique_ptr<ThreadCheckerClass> thread_checker_class(
        new ThreadCheckerClass);

    // Verify that DoStuff doesn't assert when called on a different thread after
    // a call to DetachFromThread.
    thread_checker_class->DetachFromThread();
    CallDoStuffOnThread call_on_thread(thread_checker_class.get());

    call_on_thread.Start();
    call_on_thread.Join();
}

#if GTEST_HAS_DEATH_TEST || !ENABLE_THREAD_CHECKER

void ThreadCheckerClass::MethodOnDifferentThreadImpl()
{
    std::unique_ptr<ThreadCheckerClass> thread_checker_class(
        new ThreadCheckerClass);

    // DoStuff should assert in debug builds only when called on a
    // different thread.
    CallDoStuffOnThread call_on_thread(thread_checker_class.get());

    call_on_thread.Start();
    call_on_thread.Join();
}

#if ENABLE_THREAD_CHECKER
TEST(ThreadCheckerDeathTest, MethodNotAllowedOnDifferentThreadInDebug)
{
    ASSERT_DEATH({
        ThreadCheckerClass::MethodOnDifferentThreadImpl();
    },
        "");
}
#else
TEST(ThreadCheckerTest, MethodAllowedOnDifferentThreadInRelease)
{
    ThreadCheckerClass::MethodOnDifferentThreadImpl();
}
#endif // ENABLE_THREAD_CHECKER

void ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl()
{
    std::unique_ptr<ThreadCheckerClass> thread_checker_class(
        new ThreadCheckerClass);

    // DoStuff doesn't assert when called on a different thread
    // after a call to DetachFromThread.
    thread_checker_class->DetachFromThread();
    CallDoStuffOnThread call_on_thread(thread_checker_class.get());

    call_on_thread.Start();
    call_on_thread.Join();

    // DoStuff should assert in debug builds only after moving to
    // another thread.
    thread_checker_class->DoStuff();
}

#if ENABLE_THREAD_CHECKER
TEST(ThreadCheckerDeathTest, DetachFromThreadInDebug)
{
    ASSERT_DEATH({
        ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl();
    },
        "");
}
#else
TEST(ThreadCheckerTest, DetachFromThreadInRelease)
{
    ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl();
}
#endif // ENABLE_THREAD_CHECKER

#endif // GTEST_HAS_DEATH_TEST || !ENABLE_THREAD_CHECKER

// Just in case we ever get lumped together with other compilation units.
#undef ENABLE_THREAD_CHECKER

} // namespace base
